use aspect_db_sql_migration::OnConflict;
use sea_orm::{ActiveValue, DatabaseConnection, EntityTrait};

use aspect_codec::Codec;
use aspect_db_sql::state_prop;

use crate::errors::*;

pub struct StatePropDatastore {
    dbc: DatabaseConnection,
}

impl StatePropDatastore {
    pub fn new(dbc: DatabaseConnection) -> Result<Self, Error> {
        Ok(Self { dbc })
    }

    /// Sets a property in the datastore.
    pub async fn set_prop<C: Codec>(&self, key: &str, val: &C) -> Result<(), Error> {
        let enc = aspect_codec::encode_to_vec(val)?;

        let am = state_prop::ActiveModel {
            key: ActiveValue::Set(key.to_owned()),
            value: ActiveValue::Set(enc),
        };

        state_prop::Entity::insert(am)
            .on_conflict(
                OnConflict::column(state_prop::Column::Key)
                    .update_column(state_prop::Column::Value)
                    .to_owned(),
            )
            .exec(&self.dbc)
            .await?;
        Ok(())
    }

    /// Sets a property in the datastore if it's not already set.
    pub async fn set_prop_ifne<C: Codec>(&self, key: &str, val: &C) -> Result<(), Error> {
        let enc = aspect_codec::encode_to_vec(val)?;

        let am = state_prop::ActiveModel {
            key: ActiveValue::Set(key.to_owned()),
            value: ActiveValue::Set(enc),
        };

        state_prop::Entity::insert(am)
            .on_conflict(
                OnConflict::column(state_prop::Column::Key)
                    .do_nothing()
                    .to_owned(),
            )
            .exec(&self.dbc)
            .await?;
        Ok(())
    }

    /// Gets a property in the datastore.
    pub async fn get_prop<C: Codec>(&self, key: &str) -> Result<Option<C>, Error> {
        let ent = state_prop::Entity::find_by_id(key.to_owned())
            .one(&self.dbc)
            .await?;

        match ent {
            Some(v) => Ok(Some(aspect_codec::decode::<C>(&v.value)?)),
            None => Ok(None),
        }
    }

    /// Gets a property in the datastore, returning an error if missing.
    pub async fn get_prop_ok<C: Codec>(&self, key: &str) -> Result<C, Error> {
        self.get_prop::<C>(key)
            .await?
            .ok_or(Error::MissingKey(key.to_owned()))
    }
}
